import time
import re
import queue
from queue import Queue
from threading import Event, Thread

from ollama_client import OllamaClient
from instruction_mode import InstructionMode


class NaturalLanguageProcessing:
    def __init__(
            self,
            # 队列啥的
            is_recording_event: Event,
            nlp_stopped_event: Event,
            exit_event: Event,
            
            stt_text_queue: Queue,
            tts_text_queue: Queue,
            playmusic_filename_queue: Queue,
            playeffect_filename_queue: Queue,

            assistant_name='小智',
            user_name="用户",
            host="localhost",
            port="11434",
            model="qwen3:4b-instruct",
            keep_alive=300,
            think=False,
            num_ctx=4096,
            temperature=0.7,
            top_p=0.8,
            top_k=20,
            repeat_penalty=1,
            is_default_options=True,
            clean_markdown=False,
            # 可扩展，更换 openai 需要手写一个客户端，使用格式需要与 OllamaClient 相同
            llm_client=OllamaClient,
            ):
        # 队列啥的
        self.is_recording_event = is_recording_event
        self.nlp_stopped_event = nlp_stopped_event
        self.exit_event = exit_event
        
        self.stt_text_queue = stt_text_queue
        self.tts_text_queue = tts_text_queue
        self.playmusic_filename_queue = playmusic_filename_queue
        self.playeffect_filename_queue = playeffect_filename_queue

        # 打断标志
        self.was_interrupted = False

        # 名称
        self.assistant_name = assistant_name
        self.user_name = user_name

        self.host = host
        self.port = port
        self.model = model
        self.keep_alive = keep_alive
        self.think = think
        self.num_ctx = num_ctx
        self.temperature = temperature
        self.top_p = top_p
        self.top_k = top_k
        self.repeat_penalty = repeat_penalty
        self.is_default_options = is_default_options
        self.clean_markdown = clean_markdown
        # 一轮指令模式停止标志, 停止后设置为False,在下文关联is_recording_event
        self.stop_instruction_mode_event = Event()
        self.instruction_mode_start_event = Event()

        # 实例化 llm_client
        self.llm_client = llm_client(
            host=self.host,
            port=self.port,
            model=self.model,
            keep_alive=self.keep_alive,
            think=self.think,
            num_ctx=self.num_ctx,
            temperature=self.temperature,
            top_p=self.top_p,
            top_k=self.top_k,
            repeat_penalty=self.repeat_penalty,
            is_default_options=self.is_default_options,
            clean_markdown=self.clean_markdown,
            stop_event=self.is_recording_event,
            stop_event2=self.exit_event,
        )

        text = self.llm_client.single_chat_print(user_prompt="好的，现在测试你作为大模型的语言理解能力。现在请你严格按照要求回复单个字，且只能回复这个字：好。", is_printing=False)
        # print(text)
        # 冒充初始化完成标志
        self.nlp_stopped_event.set()
        if text == "好":
            print(f"✅ 大语言模型 \t| {self.model} \t→ 连接成功")
        else:
            print(f"✅ 大语言模型 \t| {self.model} \t→ 连接不太成功，它可能有点笨……")

    def _merger(self, response, is_printing=False):
        # 记录是否进入循环，决定chunks是否是剩余内容
        was_loop = False
        # 合并token长度
        tokens = 0
        # 分为两级合并, 短一点比较适合 CosyVoice 输出语音
        tokens_1 = 17
        tokens_2 = 29
        chunks = ''
        for chunk in response:
            was_loop = True
            if is_printing:
                print(chunk, end="", flush=True)
            tokens += 1
            chunks += chunk
            if tokens >= tokens_2 and chunk and chunk[-1] in {' ', '：', ':', '、', '”', '）', ')'}:
                yield(chunks)
                chunks = ''
                tokens = 0
            elif tokens >= tokens_1 and chunk and chunk[-1] in {'\n', ';', '。', '；', ',', '，', '！', '!', '?', '？'}:
                yield(chunks)
                chunks = ''
                tokens = 0

        # 处理剩余内容
        if chunks and was_loop:
            yield(chunks)
        if is_printing:
            print()

    def _rename_first_chinese_chars(self, text, n=4):
        """
        重命名文本中的前n个中文字符，例如让你好小智连在一起
        """
         # 定义需要检查的前缀列表
        target_prefixes = {
            "你好小智", "你好小志", "你好小只", "你好小子", 
            "你好小资", "你好小字", "你好孝子", "你好小石"}
 
        # 提取所有汉字和位置
        # 存储(索引, 汉字)元组
        chinese_chars = []
        for i, char in enumerate(text):
            if '\u4e00' <= char <= '\u9fa5':
                chinese_chars.append((i, char))

        # 检查是否至少有n个中文字符
        if len(chinese_chars) >= n:
            # 提取前n个中文字符
            prefix = ''.join([char for _, char in chinese_chars[:n]])
            if prefix in target_prefixes:
                # 找到第n个汉字的位置
                nth_char_index = chinese_chars[n-1][0]
                # 提取第n个汉字后的所有字符
                after_nth_char = text[nth_char_index+1:]
                # 返回新的字符串
                return f"你好小智{after_nth_char}"
        return text
    
    def _instruction_event(self):
        while not self.exit_event.is_set():
            # 等待上一轮指令模式真正退出（下一轮指令的开始）
            self.instruction_mode_start_event.wait()
            self.instruction_mode_start_event.clear()
            self.is_recording_event.wait()
            # 指令模式需要退出
            self.stop_instruction_mode_event.set()

    def _to_stop_instruction(self):
        to_stop_instruction_thread = Thread(target=self._instruction_event, daemon=True)
        to_stop_instruction_thread.start()

    def remove_emojis_and_music(self, text):
        """
        移除字符串中情感符号和音乐符号
        
        参数:
            text (str): 包含符号的字符串
            
        返回:
            str: 去除指定符号后的字符串
        """
        # 包含情感符号和音乐符号的Unicode范围
        pattern = re.compile("["
                            # 情感表情符号
                            u"\U0001F600-\U0001F64F"  # 核心情感表情
                            u"\U0001F910-\U0001F91F"  # 面部表情扩展
                            u"\U0001F920-\U0001F92F"  # 情感相关手势
                            u"\U0001F610-\U0001F61F"  # 面部情感细节
                            u"\u2639-\u263B"          # 简单表情符号
                            u"\u2764"                  # 爱心符号
                            
                            # 音乐符号
                            u"\U0001F3B0-\U0001F3B6"  # 乐器符号
                            u"\U0001F3BC-\U0001F3BF"  # 音乐相关符号
                            u"\u266A-\u266F"          # 基本音乐符号（音符等）
                            u"\U0001F170-\U0001F171"  # 音乐符号变体
                            u"\U0001F940-\U0001F94F"  # 音乐相关符号
                            "]+", flags=re.UNICODE)
        
        return pattern.sub(r'', text)

    def _choice_mode(self, input_text):
        # 前处理
        input_text_new = self._rename_first_chinese_chars(input_text)

        input_text_new = self.remove_emojis_and_music(input_text_new)

        if input_text_new=="" or input_text_new.isspace():
            print(f"{self.user_name}没有说话")
            return

        print(f"{self.user_name}: {input_text_new}")

        if input_text_new.startswith("你好小智"):
            # 进入指令模式,该模式需要大模型回复特定格式内容。包含了指令执行，错误回复处理
            self.stop_instruction_mode_event.clear()
            self.instruction_mode_start_event.set()

            instruction_mode = InstructionMode(
                llm_client=self.llm_client, 
                input_text=input_text_new, 
                assistant_name=self.assistant_name,
                tts_text_queue=self.tts_text_queue,
                playmusic_filename_queue=self.playmusic_filename_queue,
                playeffect_filename_queue=self.playeffect_filename_queue,
                stop_instruction_event=self.stop_instruction_mode_event,
                )
            instruction_mode.run()
            # 指令模式退出
        else:
            # 聊天模式
            with open('assets/prompts/chat_prompt.txt', 'r', encoding='utf-8') as f:
                chat_system_prompt = f.read()
            print(f"{self.assistant_name}：", end='')
            response = self.llm_client.chat(user_prompt=input_text_new, system_prompt=chat_system_prompt)
            response_merged = self._merger(response, is_printing=True)
            for chunk in response_merged:
                self.tts_text_queue.put(chunk)
    
    def _clear_queue(self, queue: Queue):
        """清空队列中的所有音频文件"""
        while not queue.empty():
            try:
                queue.get_nowait()
                queue.task_done()
            except queue.Empty:
                break

    def dialogue(self, input_text):
        """
        一般对话
        """
        self._choice_mode(input_text)

    def dialogue_loop(self):
        """
        对话循环
        """
        # 指令模式停止标志线程，指令模式什么时候停要听它的
        self._to_stop_instruction()
        
        while not self.exit_event.is_set():
            if self.is_recording_event.is_set():
                if not self.was_interrupted:
                    self._clear_queue(self.stt_text_queue)
                    self.nlp_stopped_event.set()
                    self.was_interrupted = True
                time.sleep(0.02)
                continue
            else:
                self.was_interrupted = False

            try:
                # 循环等待时间
                text = self.stt_text_queue.get(timeout=0.02)

                # 再次检查录音状态
                if self.is_recording_event.is_set():
                    self._clear_queue(self.stt_text_queue)
                    self.nlp_stopped_event.set()
                    self.was_interrupted = True
                    continue

                # 处理语言
                self._choice_mode(text)
                self.stt_text_queue.task_done()
            except queue.Empty:
                continue

    def start(self):
        self.dialogue_loop()


if __name__ == "__main__":
    pass